package com.hunttown.common.sharding.client.manager;

import com.hunttown.common.sharding.client.RouteFactor;
import com.hunttown.common.sharding.client.annotation.ISharding;
import com.hunttown.common.sharding.client.annotation.ShardResult;
import com.hunttown.common.sharding.client.annotation.Sharding;
import com.hunttown.common.sharding.client.exception.RouteException;
import com.hunttown.common.sharding.client.router.Router;
import com.hunttown.common.sharding.client.support.RouteResult;
import com.hunttown.common.sharding.client.transaction.MultipleTransactionService;
import com.hunttown.common.sharding.client.util.BeanPropertyAccessUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;

public abstract class MultipleTransactionBaseManager {
    private final Log log = LogFactory.getLog(MultipleTransactionBaseManager.class);
    private MultipleTransactionService transactionManagerService;
    private Router<RouteFactor> router;
    private boolean returnWhenException = false;
    private boolean returnWhenUniqueException = false;

    public MultipleTransactionBaseManager() {
    }

    public final Object handleDataBase(Object obj, Method handleMethod, Object[] args) throws InvocationTargetException, IllegalAccessException {
        if (args != null && args.length != 0) {
            String firstParaType = this.getObjectType(handleMethod.getParameterTypes()[0]);
            boolean cantainDsName = args[0] == null && firstParaType.equalsIgnoreCase("string");
            int reqStartWith = cantainDsName ? 1 : 0;
            Map<String, Object[]> objMap = this.wrapRequestParamByDB(args, reqStartWith);
            if (objMap.size() < 1) {
                this.log.error("分库分表数据分配后数据源列表为空！请检查数据以及配置！");
            }

            Iterator<String> keyIt = objMap.keySet().iterator();
            String returnType = this.getObjectType(handleMethod.getReturnType());
            Object result = null;
            Object returnObj = null;
            ArrayList returnList = null;

            while(true) {
                while(true) {
                    while(true) {
                        String dbName;
                        do {
                            if (!keyIt.hasNext()) {
                                if (returnList != null && returnObj == null) {
                                    returnObj = returnList;
                                }

                                return returnObj;
                            }

                            dbName = (String)keyIt.next();
                        } while(!this.needExecute((Object[])objMap.get(dbName), reqStartWith));

                        Object[] objs = (Object[])objMap.get(dbName);
                        result = this.executeDataBase(obj, handleMethod, objs, dbName);
                        if (result instanceof ShardResult) {
                            ShardResult shardResult = (ShardResult)result;
                            if (shardResult.getErrorCode() != 0 && this.returnWhenUniqueException || !shardResult.isSuccess() && this.returnWhenException) {
                                if ("boolean".equals(returnType)) {
                                    if (this.returnWhenUniqueException && shardResult.getErrorCode() == 1) {
                                        return false;
                                    }

                                    return shardResult.isSuccess();
                                }

                                if ("integer".equals(returnType)) {
                                    return shardResult.getErrorCode();
                                }
                            }

                            if ("boolean".equals(returnType) && (returnObj == null || Boolean.parseBoolean(String.valueOf(returnObj)))) {
                                returnObj = shardResult.isSuccess();
                            } else if ("integer".equals(returnType) && (returnObj == null || Integer.parseInt(String.valueOf(returnObj)) < shardResult.getErrorCode())) {
                                returnObj = shardResult.getErrorCode();
                            }
                        } else if (!"boolean".equals(returnType) || returnObj != null && result != null && !Boolean.parseBoolean(String.valueOf(returnObj))) {
                            if ("integer".equals(returnType) && (returnObj == null || result == null || Integer.parseInt(String.valueOf(returnObj)) < Integer.parseInt(String.valueOf(result)))) {
                                returnObj = result;
                            } else if ("list".equals(returnType) && result != null) {
                                if (returnList == null) {
                                    returnList = new ArrayList();
                                }

                                returnList.addAll((List)result);
                            } else {
                                returnObj = result;
                            }
                        } else {
                            returnObj = result;
                        }
                    }
                }
            }
        } else {
            this.log.error("调用方法[" + handleMethod.getName() + "]的数据库处理异常：请求参数为空！");
            return null;
        }
    }

    private Map<String, Object[]> wrapRequestParamByDB(Object[] args, int reqStartWith) {
        Map<String, Object[]> objMap = new HashMap();
        boolean isDbParam = false;

        int i;
        Object arg;
        for(i = reqStartWith; i < args.length; ++i) {
            arg = args[i];
            isDbParam = false;
            if (arg != null) {
                if (arg instanceof List) {
                    List<Object> shardList = (List)arg;
                    if (shardList != null && shardList.size() > 0) {
                        Object shardObj = shardList.get(0);
                        if (shardObj instanceof Sharding || shardObj instanceof ISharding) {
                            isDbParam = true;
                            Iterator i$ = shardList.iterator();

                            while(i$.hasNext()) {
                                Object shard = i$.next();
                                String dbString = this.getRoutedSingleDataSource(shard);
                                Object[] objDb = (Object[])objMap.get(dbString);
                                if (objDb == null) {
                                    objDb = new Object[args.length];
                                    objMap.put(dbString, objDb);
                                }

                                List<Object> itemList = (List)objDb[i];
                                if (itemList == null) {
                                    itemList = new ArrayList();
                                    objDb[i] = itemList;
                                }

                                ((List)itemList).add(shard);
                                if (objDb[0] == null && reqStartWith == 1) {
                                    objDb[0] = dbString;
                                }
                            }
                        }
                    }
                } else {
                    Object[] objDb;
                    String dbString;
                    if (arg instanceof Map) {
                        Map<String, Object> shardMap = (Map)arg;
                        if (shardMap.containsKey("shardField")) {
                            dbString = this.getRoutedSingleDataSource(shardMap);
                            if (!shardMap.containsKey("itemInAllDataSources") || !"true".equalsIgnoreCase(String.valueOf(shardMap.get("itemInAllDataSources")))) {
                                isDbParam = true;
                                objDb = (Object[])objMap.get(dbString);
                                if (objDb == null) {
                                    objDb = new Object[args.length];
                                    objMap.put(dbString, objDb);
                                }

                                objDb[i] = arg;
                                if (objDb[0] == null && reqStartWith == 1) {
                                    objDb[0] = dbString;
                                }
                            }
                        }
                    } else if (arg instanceof Sharding || arg instanceof ISharding) {
                        boolean isAllDs = true;
                        if (arg instanceof Sharding) {
                            Sharding shardingObj = (Sharding)arg;
                            isAllDs = shardingObj.isItemInAllDataSources();
                        } else {
                            ISharding shardingObj = (ISharding)arg;
                            isAllDs = shardingObj.isItemInAllDataSources();
                        }

                        if (!isAllDs) {
                            isDbParam = true;
                            dbString = this.getRoutedSingleDataSource(arg);
                            objDb = (Object[])objMap.get(dbString);
                            if (objDb == null) {
                                objDb = new Object[args.length];
                                objMap.put(dbString, objDb);
                            }

                            objDb[i] = arg;
                            if (objDb[0] == null && reqStartWith == 1) {
                                objDb[0] = dbString;
                            }
                        }
                    }
                }
            }

            if (isDbParam) {
                args[i] = null;
            }
        }

        for(i = reqStartWith; i < args.length; ++i) {
            arg = args[i];
            Object[] objDb;
            if (arg != null && objMap.size() > 0) {
                for(Iterator dbIt = objMap.keySet().iterator(); dbIt.hasNext(); objDb[i] = arg) {
                    objDb = (Object[])objMap.get(dbIt.next());
                }
            }
        }

        return objMap;
    }

    private Object executeDataBase(final Object obj, final Method handleMethod, final Object[] args, String dbKey) {
        Object result = null;
        if (!handleMethod.getName().startsWith("doTransaction")) {
            try {
                result = handleMethod.invoke(obj, args);
            } catch (IllegalArgumentException var9) {
                this.log.error(handleMethod.getName() + " IllegalArgumentException", var9);
            } catch (IllegalAccessException var10) {
                this.log.error(handleMethod.getName() + " IllegalAccessException", var10);
            } catch (InvocationTargetException var11) {
                this.log.error(handleMethod.getName() + " InvocationTargetException", var11);
            } catch (Throwable var12) {
                this.log.error(handleMethod.getName() + " error!", var12);
            }

            return result;
        } else {
            final ShardResult shardResult = new ShardResult();
            shardResult.setSuccess(true);
            shardResult.setErrorCode(0);
            PlatformTransactionManager transManager = (PlatformTransactionManager)this.transactionManagerService.getTransactionManagers().get(dbKey);
            if (transManager == null) {
                this.log.error("数据源[" + dbKey + "]没有对应的事务管理器！");
                return null;
            } else {
                try {
                    TransactionTemplate template = new TransactionTemplate(transManager);
                    template.execute(new TransactionCallbackWithoutResult() {
                        protected void doInTransactionWithoutResult(TransactionStatus status) {
                            try {
                                handleMethod.invoke(obj, args);
                            } catch (IllegalArgumentException var4) {
                                shardResult.setSuccess(false);
                                shardResult.setErrorCode(2);
                                MultipleTransactionBaseManager.this.log.error(handleMethod.getName() + " IllegalArgumentException", var4);
                                throw new RuntimeException(handleMethod.getName() + " IllegalArgumentException", var4);
                            } catch (IllegalAccessException var5) {
                                shardResult.setSuccess(false);
                                shardResult.setErrorCode(2);
                                MultipleTransactionBaseManager.this.log.error(handleMethod.getName() + " IllegalAccessException", var5);
                                throw new RuntimeException(handleMethod.getName() + " IllegalAccessException", var5);
                            } catch (InvocationTargetException var6) {
                                Throwable tb = var6.getCause();
                                if (tb != null && tb.getMessage().indexOf("Duplicate entry") != -1) {
                                    MultipleTransactionBaseManager.this.log.error("方法[" + handleMethod.getName() + "]因为唯一索引插入失败，认为是重复的请求！！");
                                    MultipleTransactionBaseManager.this.log.error(tb.getMessage());
                                    shardResult.setErrorCode(1);
                                    throw new RuntimeException("方法[" + handleMethod.getName() + "]因为唯一索引插入失败，认为是重复的请求！！");
                                } else {
                                    shardResult.setSuccess(false);
                                    shardResult.setErrorCode(2);
                                    MultipleTransactionBaseManager.this.log.error(handleMethod.getName() + " InvocationTargetException", var6);
                                    throw new RuntimeException(handleMethod.getName() + " InvocationTargetException", var6);
                                }
                            } catch (Exception var7) {
                                shardResult.setSuccess(false);
                                shardResult.setErrorCode(2);
                                MultipleTransactionBaseManager.this.log.error(handleMethod.getName() + " error!", var7);
                                throw new RuntimeException(handleMethod.getName() + " error!", var7);
                            }
                        }
                    });
                } catch (Throwable var13) {
                    if (var13.getCause() != null) {
                        this.log.error("方法[" + handleMethod.getName() + "]处理失败，已经回滚！", var13);
                    }
                }

                return shardResult;
            }
        }
    }

    private boolean needExecute(Object[] objs, int reqStartWith) {
        if (objs == null) {
            return false;
        } else {
            for(int i = reqStartWith; i < objs.length; ++i) {
                Object obj = objs[i];
                if (obj != null) {
                    return true;
                }
            }

            return false;
        }
    }

    public void setTransactionManagerService(MultipleTransactionService transactionManagerService) {
        this.transactionManagerService = transactionManagerService;
    }

    public Router<RouteFactor> getRouter() {
        return this.router;
    }

    public void setRouter(Router<RouteFactor> router) {
        this.router = router;
    }

    protected String getRoutedSingleDataSource(Object parameterObj) throws RouteException {
        RouteFactor routeFactor = this.loadRouteFactor(parameterObj);
        RouteResult result = this.getRouter().doRoute(routeFactor);
        if (!CollectionUtils.isEmpty(result.getResourceIdentities())) {
            List<String> dataSources = result.getResourceIdentities();
            if (dataSources.size() > 1) {
                throw new RouteException("Multiple DataSources were selected by the Router!");
            }

            if (dataSources.size() == 1) {
                return (String)dataSources.get(0);
            }
        }

        throw new RouteException("can not find routed datasource by your parameter object!");
    }

    protected RouteFactor loadRouteFactor(Object parameterObj) throws RouteException {
        try {
            String logicalTableName = "";
            Object routeField = null;
            if (parameterObj instanceof Map) {
                Map parametersMap = (Map)parameterObj;
                logicalTableName = (String)parametersMap.get("logicTableName");
                if (parametersMap.get("shardField") != null) {
                    routeField = parametersMap.get(parametersMap.get("shardField"));
                }
            } else if (parameterObj != null) {
                if (parameterObj instanceof Sharding) {
                    Sharding sharding = (Sharding)parameterObj;
                    if (!StringUtils.isNotBlank(sharding.getLogicTableName())) {
                        throw new RouteException("the Sharding Object don't contain logicTableName field,please check your parameterObject!");
                    }

                    logicalTableName = sharding.getLogicTableName();
                    routeField = BeanPropertyAccessUtil.getPropertyValue(sharding.getShardField(), sharding);
                } else if (parameterObj instanceof ISharding) {
                    ISharding sharding = (ISharding)parameterObj;
                    if (!StringUtils.isNotBlank(sharding.getLogicTableName())) {
                        throw new RouteException("the ISharding Object don't contain logicTableName field,please check your parameterObject!");
                    }

                    logicalTableName = sharding.getLogicTableName();
                    routeField = BeanPropertyAccessUtil.getPropertyValue(sharding.getShardField(), sharding);
                } else {
                    logicalTableName = (String)BeanPropertyAccessUtil.getPropertyValue("logicTableName", parameterObj);
                    routeField = BeanPropertyAccessUtil.getPropertyValue("shardField", parameterObj);
                }
            }

            RouteFactor routeFactor = new RouteFactor(logicalTableName);
            routeFactor.addRouteField("shard_field", routeField);
            routeFactor.setParameter(parameterObj);
            return routeFactor;
        } catch (Throwable var5) {
            throw new RouteException("load route factor error,please check your parameterObject!", var5);
        }
    }

    public String getPhysicalTableName(RouteFactor routeFactor) {
        RouteResult routeResult = this.getRouter().doRoute(routeFactor);
        return routeResult != null ? routeResult.getPhysicalTableName() : null;
    }

    public boolean isReturnWhenException() {
        return this.returnWhenException;
    }

    public void setReturnWhenException(boolean returnWhenException) {
        this.returnWhenException = returnWhenException;
    }

    public void setReturnWhenUniqueException(boolean returnWhenUniqueException) {
        this.returnWhenUniqueException = returnWhenUniqueException;
    }

    protected Map<String, List<String>> getAllDataSourcesAndTables(String logicTableName) {
        return this.getRouter().getRouteInfo(logicTableName);
    }

    private String getObjectType(Class clazz) {
        if (!clazz.getName().equals("boolean") && clazz.getName().indexOf("Boolean") == -1) {
            if (!clazz.getName().equals("int") && clazz.getName().indexOf("Integer") == -1) {
                if (!clazz.getName().equals("java.util.List") && !clazz.getName().equals("java.util.ArrayList")) {
                    return clazz.getName().equals("java.lang.String") ? "string" : "object";
                } else {
                    return "list";
                }
            } else {
                return "integer";
            }
        } else {
            return "boolean";
        }
    }
}